package loader

import (
	"os"
	"path/filepath"
	"kumachan/interpreter/lang/common/source"
)


func buildHierarchy (
	raw_mod  ModuleThunk,
	fs       FileSystem,
	ctx      context,
	idx      Index,
) (*Module, bool, *Error) {
	/* 1. Try to parse the content to get an AST */
	var mod_path = raw_mod.ModPath
	var manifest_path = filepath.Join(mod_path, ManifestFileName)
	var manifest_file = manifestSourceFile { manifest_path }
	var module_name = raw_mod.Manifest.UniqueId
	var file_info = raw_mod.FileInfo
	var ast_root_node, err2 = raw_mod.Content.Load(ctx)
	if err2 != nil { return nil, false, err2 }
	/* 2. Check the module name according to ancestor modules */
	for _, ancestor := range ctx.BreadCrumbs {
		if ancestor.ModuleName == module_name {
			/* 2.1. If there is an ancestor with the same name: */
			if os.SameFile(ancestor.FileInfo, file_info) {
				/* 2.1.1. If it corresponds to the same source file, */
				/*        throw an error of circular import. */
				return nil, false, &Error {
					Context:  ctx,
					Concrete: E_DepModCircular {
						ModuleName: module_name,
					},
				}
			} else {
				/* 2.1.2. Otherwise, throw an error of module name conflict. */
				return nil, false, &Error {
					Context:  ctx,
					Concrete: E_DepModConflict {
						ModuleName: module_name,
						FilePath1:  ancestor.FilePath,
						FilePath2:  mod_path,
					},
				}
			}
		}
	}
	var current_breadcrumbs = append(ctx.BreadCrumbs, ancestor {
		ModuleName: module_name,
		FileInfo:   file_info,
		FilePath:   mod_path,
	})
	/* 3. Check the module name according to previous sibling (sub)modules */
	var existing, exists = idx[module_name]
	if exists {
		/* 3.1. If there is a sibling (sub)module with the same name */
		if os.SameFile(existing.FileInfo, file_info) {
			/* 3.1.1. If it corresponds to the same source file, */
			/*        which indicates the module has already been loaded, */
			/*        return the loaded module. */
			return existing, true, nil
		} else {
			/* 3.1.2. Otherwise, throw an error of module name conflict. */
			return nil, false, &Error {
				Context: ctx,
				Concrete: E_DepModConflict {
					ModuleName: module_name,
					FilePath1:  existing.Path,
					FilePath2:  mod_path,
				},
			}
		}
	} else {
		/* 3.2. Otherwise, load all submodules of current module */
		/*      and then return the current module. */
		var imported_map = make(map[string] *Module)
		var imported_set = make(map[string] bool)
		importStdlib(&imported_map, &imported_set, idx)
		for i, dep := range raw_mod.Manifest.Dependencies {
			var this = dep.ImportName
			var rel_path = dep.ModulePath
			var loc = source.Location {
				File: manifest_file,
				Pos:  manifestDepPos(uint32(i)),
			}
			var this_ctx = context {
				ImportPoint: loc,
				ImportName:  this,
				BreadCrumbs: current_breadcrumbs,
			}
			var _, exists = imported_map[this]
			if exists || this == SelfModuleImportName {
				return nil, false, &Error {
					Context:  this_ctx,
					Concrete: E_DepNameDuplicate {
						ImportName: this,
					},
				}
			}
			var this_path string
			if file_info.IsDir() {
				this_path = filepath.Join(mod_path, rel_path)
			} else {
				this_path = filepath.Join(filepath.Dir(mod_path), rel_path)
			}
			var this_mod, err = loadModule(this_path, fs, this_ctx, idx)
			if err != nil {
				// Bubble errors
				return nil, false, err
			} else {
				// Register the imported module
				var this_name = string(this_mod.Name)
				if imported_set[this_name] { return nil, false, &Error {
					Context: this_ctx,
					Concrete: E_DepModDuplicate {
						ModuleName: this_name,
					},
				} }
				imported_set[this_name] = true
				imported_map[this] = this_mod
				idx[this_name] = this_mod
			}
		}
		var mod = &Module {
			Name:     module_name,
			Path:     mod_path,
			AST:      ast_root_node,
			ImpMap:   imported_map,
			FileInfo: file_info,
			Manifest: raw_mod.Manifest,
		}
		idx[module_name] = mod
		return mod, false, nil
	}
}


